/*
 * File:   main.cpp
 * Author: Tung Mai
 *
 * Created on May 6, 2010, 2:12 PM
 */

#include <unistd.h>
#include <AudioToolbox/AudioToolbox.h>

static const int kNumberBuffers = 3;

struct AQRecorderState {
    AudioStreamBasicDescription  mDataFormat;
    AudioQueueRef                mQueue;
    AudioQueueBufferRef          mBuffers[kNumberBuffers];
    AudioFileID                  mAudioFile;
    UInt32                       bufferByteSize;
    SInt64                       mCurrentPacket;
    bool                         mIsRunning;
};

static void HandleInputBuffer(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
        const AudioTimeStamp *inStartTime, UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
{
    AQRecorderState *pAqData = (AQRecorderState *) aqData;
    OSStatus err;

    // If the audio queue buffer contains CBR data, calculate the number of packets in the buffer
    if (inNumPackets == 0 && pAqData->mDataFormat.mBytesPerPacket != 0)
        inNumPackets = inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;

    // Writing an audio queue buffer to disk
    err = AudioFileWritePackets(pAqData->mAudioFile, false, inBuffer->mAudioDataByteSize,
            inPacketDesc, pAqData->mCurrentPacket, &inNumPackets, inBuffer->mAudioData);

    // If successful in writing the audio data, increment the audio data file’s packet
    // index to be ready for writing the next buffer's worth of audio data.
    if (err == noErr)
        pAqData->mCurrentPacket += inNumPackets;

    if (pAqData->mIsRunning == 0)
        return;

    // Enqueuing an audio queue buffer after writing to disk
    AudioQueueEnqueueBuffer(pAqData->mQueue, inBuffer, 0, NULL);
}

void DeriveBufferSize(AudioQueueRef audioQueue, AudioStreamBasicDescription &ASBDescription,
        Float64 seconds, UInt32 *outBufferSize)
{
    // the upper bound is set to 320 KB. This corresponds to approximately five seconds
    // of stereo, 24 bit audio at a sample rate of 96 kHz.
    static const int maxBufferSize = 0x50000;

    // For CBR audio data, get the (constant) packet size from the AudioStreamBasicDescription structure
    int maxPacketSize = ASBDescription.mBytesPerPacket;

    // For VBR audio data, query the audio queue to get the estimated maximum packet size
    if (maxPacketSize == 0)
    {
        UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
        AudioQueueGetProperty(audioQueue, kAudioConverterPropertyMaximumOutputPacketSize,
            &maxPacketSize, &maxVBRPacketSize);
    }

    // Derive the buffer size, in bytes
    Float64 numBytesForTime = ASBDescription.mSampleRate * maxPacketSize * seconds;

    // Limit the buffer size, if needed, to the previously set upper bound
    *outBufferSize = UInt32 (numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize);                     // 9
}

OSStatus SetMagicCookieForFile(AudioQueueRef inQueue, AudioFileID inFile)
{
    OSStatus result = noErr;
    UInt32 cookieSize;

    if (AudioQueueGetPropertySize(inQueue, kAudioQueueProperty_MagicCookie, &cookieSize) == noErr)
    {
        char* magicCookie = (char *) malloc(cookieSize);
        if (AudioQueueGetProperty(inQueue, kAudioQueueProperty_MagicCookie,
            magicCookie, &cookieSize) == noErr)
        {
            result = AudioFileSetProperty(inFile, kAudioFilePropertyMagicCookieData,
                    cookieSize, magicCookie);
        }
        free(magicCookie);
    }

    return result;
}

/*
 *
 */
int main(int argc, char** argv)
{
    // Set up an audio format for recording
    AQRecorderState aqData;
    aqData.mDataFormat.mFormatID = kAudioFormatLinearPCM;
    aqData.mDataFormat.mSampleRate = 22050.0;//44100.0;
    aqData.mDataFormat.mChannelsPerFrame = 1;
    aqData.mDataFormat.mBitsPerChannel = 16;
    aqData.mDataFormat.mBytesPerPacket = aqData.mDataFormat.mBytesPerFrame =
            aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
    aqData.mDataFormat.mFramesPerPacket = 1;
    aqData.mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian |
            kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;

    AudioFileTypeID fileType = kAudioFileAIFFType;

    // Creating a recording audio queue
    AudioQueueNewInput(&aqData.mDataFormat, HandleInputBuffer, &aqData, NULL,
        kCFRunLoopCommonModes, 0, &aqData.mQueue);

    // Getting the audio format from an audio queue
    UInt32 dataFormatSize = sizeof(aqData.mDataFormat);

    // When running as root, lower privilege here
    AudioQueueGetProperty(aqData.mQueue, kAudioConverterCurrentOutputStreamDescription,
        &aqData.mDataFormat, &dataFormatSize);
    // Restore privilege here

    // Creating an audio file for recording
    char* filePath = "/Users/macos2/Desktop/record.aif";
    CFURLRef audioFileURL = CFURLCreateFromFileSystemRepresentation(NULL,
        (const UInt8 *) filePath, strlen(filePath), false);

    AudioFileCreateWithURL(audioFileURL, fileType, &aqData.mDataFormat,
        kAudioFileFlags_EraseFile, &aqData.mAudioFile);

    // Setting an audio queue buffer size
    DeriveBufferSize(aqData.mQueue, aqData.mDataFormat, 0.5, &aqData.bufferByteSize);

    // Preparing a set of audio queue buffers
    for (int i = 0; i < kNumberBuffers; ++i)
    {
        AudioQueueAllocateBuffer(aqData.mQueue, aqData.bufferByteSize, &aqData.mBuffers[i]);
        AudioQueueEnqueueBuffer(aqData.mQueue, aqData.mBuffers[i], 0, NULL);
    }

    // Recording audio
    aqData.mCurrentPacket = 0;
    aqData.mIsRunning = true;
    AudioQueueStart(aqData.mQueue, NULL);

    // Wait, on user interface thread, until user stops the recording
    sleep(5);

    AudioQueueStop(aqData.mQueue, true);

    aqData.mIsRunning = false;

    // Cleaning up after recording
    AudioQueueDispose(aqData.mQueue, true);
    AudioFileClose(aqData.mAudioFile);

    return (EXIT_SUCCESS);
}

